#include "../include/InpaintingThese.h"
/*
#include "../include/Masque.h"
#include "../include/Image.h"
#include "../include/Pixel.h"
*/

InpaintingThese::InpaintingThese (
	Image * _src/*= NULL*/, Masque * _msk /*= NULL*/, Image * _dest /*= NULL*/,
	int _canaux/*= 0*/, double _sigma /*= 0.5*/ )
	: src ( _src ), canaux ( _canaux ), msk ( _msk ), dest ( _dest ),
	sigma ( _sigma )
{
	if ( _src == NULL || _msk == NULL || _dest == NULL )
	{ // c'est le constructeur par défaut qui a été appelé, on ne fait rien.
		//std::cerr << "InpaintingThese::InpaintingThese () : erreur : pointeur nul donné en paramètre" << std::endl;
		return;
	}
	if ( _sigma < 0.00001 )
		sigma = 0.1; // sigma ne doit pas etre = 0 (/0)
	tailleVoisinage = (int)(6 * sigma);
	if ( _canaux <= 0 || _canaux > 3 )
		canaux = 3;
	w = _src->getL ();
	h = _src->getH ();
	bStop = false;
	
	aGgTTousCanaux = (Matrice **)calloc (
		(w+tailleVoisinage)*(h+tailleVoisinage),
		sizeof ( Matrice * ) ); // calloc () remplit aussi la zone avec des zéros
	aGgTTousCanauxOk = (int *)calloc (
		(w+tailleVoisinage)*(h+tailleVoisinage),
		sizeof ( int ) );
	aGaussienne = (float *)calloc (
		(tailleVoisinage)*(tailleVoisinage),
		sizeof ( float ) );
	aGaussienneOk = (int *)calloc (
		(tailleVoisinage)*(tailleVoisinage),
		sizeof ( int ) );
} // constructor

InpaintingThese::~InpaintingThese ()
{} // destructor

double InpaintingThese::Gaussienne ( int _x, int _y, double _sigma /*= 0.0*/ )
{
	if ( _sigma > -0.00001 && _sigma < 0.00001 )
		_sigma = sigma;
	return ( 1.0 / (2*PI*_sigma*_sigma) )
		* exp ( -1.0*(_x*_x + _y*_y) / (2*_sigma*_sigma) );
} // InpaintingThese::gaussienne ()

Matrice * InpaintingThese::calculGgras ( int _x, int _y,
	Matrice * _m22temp, Matrice * _res/*= NULL*/, double _sigma /*= 0.0*/,
	int _tailleVoisinage /*= 0*/ )
{
	//std::cerr << "*a" << std::endl;
	if ( _sigma > -0.00001 && _sigma < 0.00001 )
		_sigma = 0.5; // sigma ne doit pas etre == 0 (/0)
	if ( _tailleVoisinage < 2 )
		_tailleVoisinage = 2; // pas de voisinage négatif ou trop petit
	if ( _m22temp == NULL )
	{
		std::cerr << "InpaintingThese::calculGgras () : erreur : _m22temp ne doit pas être NULL" << std::endl;
		return NULL;
	}
	if ( _res == NULL )
		_res = new Matrice ( 2, 2 );
	_res->setValeur ( 0, 0, 0.0 );
	_res->setValeur ( 0, 1, 0.0 );
	_res->setValeur ( 1, 0, 0.0 );
	_res->setValeur ( 1, 1, 0.0 );
	int l = _tailleVoisinage/2;
	Matrice * ggTTousCanaux = NULL; // l=2, h=2 // sera créée la premiere fois, puis modifiée C MAGNIFIK
	double gaussienne;
	//Matrice * m22temp = new Matrice ( 2, 2 );
	//std::cerr << "*b" << std::endl;
	for ( int yy = -l; yy < l; ++yy )
	{
		//std::cerr << "*c :" << yy << std::endl;
		for ( int xx = -l; xx < l; ++xx )
		{
			int id = (h+_tailleVoisinage)*(_y+yy+l)+(_x+xx+l);
			if ( aGgTTousCanauxOk[id] == 0 || aGgTTousCanaux[id] == NULL )
			{
				//std::cout << "on crée aGgTTousCanaux" << std::endl;
				// si aGgTTousCanaux[id] était NULL, alors la matrice est créée
				aGgTTousCanaux[id] = dest->sommeNablaInablaIT ( _x+xx, _y+yy, _m22temp, aGgTTousCanaux[id], 3 );
				aGgTTousCanauxOk[id] = 1;
			}
			else
			{ // on réutilise la valeur qu'on avait calculée
				//std::cout << "on réutilise aGgTTousCanaux" << std::endl;
			}
			ggTTousCanaux = aGgTTousCanaux[id];
			//std::cerr << "--------------------" << std::endl;
			//std::cerr << "ggTTousCanaux :" << std::endl;
			//ggTTousCanaux->afficher ();
			int id2 = (_tailleVoisinage)*(yy+l)+(xx+l);
			if ( aGaussienneOk[id2] == 0 )
			{
				aGaussienne[id2] = Gaussienne ( xx, yy, _sigma );
				aGaussienneOk[id2] = 1;
				//std::cout << "on crée aGaussienne" << std::endl;
			}
			else
			{
				//std::cout << "on réutilise aGaussienne" << std::endl;
			}
			gaussienne = aGaussienne[id2];
			//std::cerr << "Gaussienne pour x = " << xx << ", y = " << yy << " : " << gaussienne << std::endl;
			_res->setValeur ( 0, 0, _res->getValeur ( 0, 0 ) + gaussienne * ggTTousCanaux->getValeur ( 0, 0 ) );
			_res->setValeur ( 0, 1, _res->getValeur ( 0, 1 ) + gaussienne * ggTTousCanaux->getValeur ( 0, 1 ) );
			_res->setValeur ( 1, 0, _res->getValeur ( 1, 0 ) + gaussienne * ggTTousCanaux->getValeur ( 1, 0 ) );
			_res->setValeur ( 1, 1, _res->getValeur ( 1, 1 ) + gaussienne * ggTTousCanaux->getValeur ( 1, 1 ) );
			/*std::cerr << "valeur en 0.0 : " << (gaussienne * ggTTousCanaux->getValeur ( 0, 0 )) << std::endl;
			std::cerr << "valeur en 0.1 : " << (gaussienne * ggTTousCanaux->getValeur ( 0, 1 )) << std::endl;
			std::cerr << "valeur en 1.0 : " << (gaussienne * ggTTousCanaux->getValeur ( 1, 0 )) << std::endl;
			std::cerr << "valeur en 1.1 : " << (gaussienne * ggTTousCanaux->getValeur ( 1, 1 )) << std::endl;*/
		}
	}
	//std::cerr << "###################################################" << std::endl;
	//delete m22temp;
	return _res;
}

Matrice * InpaintingThese::calculD ( float _lambda1, float _lambda2,
	Matrice * _theta1, Matrice * _theta2, Matrice * _res )
{
	float lambdaplus = std::max ( _lambda1, _lambda2 ); // en fait ca sera à rien de les différencier
	float lambdamoins = std::min ( _lambda1, _lambda2 );
	Matrice *thetaplus, *thetamoins;
	
	if ( _theta1 == NULL || _theta2 == NULL || _res == NULL )
	{
		std::cout << "InpaintingThese::calculD () : erreur : pointeur nul en paramètre" << std::endl;
		return _res;
	}
	
	if ( sqrt ( _theta1->getValeur ( 0, 0 )*_theta1->getValeur ( 0, 0 )
		+ _theta1->getValeur ( 0, 1 )*_theta1->getValeur ( 0, 1 ) )
		> sqrt ( _theta2->getValeur ( 0, 0 )*_theta2->getValeur ( 0, 0 )
		+ _theta2->getValeur ( 0, 1 )*_theta2->getValeur ( 0, 1 ) ) )
	{
		thetaplus = _theta1;
		thetamoins = _theta2;
	}
	else
	{
		thetaplus = _theta2;
		thetamoins = _theta1;
	}
	
	_res = thetamoins->mmT2 ( _res );
	double v = 1.0 / ( sqrt ( 1.0 + lambdaplus + lambdamoins ) );
	_res->multiplication ( v, _res );
	
	return _res;
}

void InpaintingThese::tout ( double _sigma/*=0.0*/, int _iterations/*=0*/,
	int _init/*=0*/, int _normvp/*=0*/,
	double _coeff/*=1.0*/, int _affichage/*=0*/,
	Image * _src/*=NULL*/, Masque * _msk/*=NULL*/, Image * _dest/*=NULL*/ )
{
	bStop = false;
	struct timeval start, end;
	long mtime, seconds, useconds;
	gettimeofday(&start, NULL);
	
	int _canaux = canaux;
	
	// EN TRAVAUX
	
	int _tailleVoisinage = tailleVoisinage;
	
	if ( _msk == NULL )
		_msk = msk;
	/*if ( _canaux <= 0 )
		_canaux = canaux;*/
	if ( _iterations <= 0 )
		_iterations = 1000;
	if ( _src == NULL )
		_src = src;
	if ( _dest == NULL )
		_dest = dest;
	if ( /*_sigma > -0.00001 &&*/ _sigma < 0.00001 )
		_sigma = sigma; // sigma ne doit pas etre == 0 (/0)
	if ( _init < 0 || _init > 2 )
		_init = 0;
	if ( _normvp < 0 || _normvp > 2 )
		_normvp = 0;
	if ( _coeff < 1.0 )
		_coeff = 1.0;
	
	std::cerr << "_msk : " << (int)_msk << std::endl;
	std::cerr << "_canaux : " << _canaux << std::endl;
	std::cerr << "_iterations : " << _iterations << std::endl;
	std::cerr << "_src : " << (int)_src << std::endl;
	std::cerr << "_dest : " << (int)_dest << std::endl;
	std::cerr << "_sigma : " << _sigma << std::endl;
	std::cerr << "_tailleVoisinage : " << _tailleVoisinage << std::endl;
	
	std::cerr << "a" << std::endl;
	Image * dest2 = _src->CopierImage (); // dest2 est créée
	Pixel * px, * px2;
	Matrice * Ggras = new Matrice ( 2, 2 );
	float * lambda1 = new float ();
	float * lambda2 = new float ();
	Matrice * theta1 = new Matrice ( 1, 2 );
	Matrice * theta2 = new Matrice ( 1, 2 );
	Matrice * D = new Matrice ( 2, 2 );
	Matrice * H = new Matrice ( 2, 2 );
	Matrice * DH = new Matrice ( 2, 2 );
	Matrice * m22temp = new Matrice ( 2, 2 );
	int h = _src->getH ();
	int w = _src->getL ();
	double p[3];
	int foo;
	int i, y, x, canal, pos;
	double v;
	
	//int xminO, xmaxO, yminO, ymaxO;
	int xmin, xmax, ymin, ymax;
	_msk->getBounds ( &xmin, &xmax, &ymin, &ymax );
	std::cerr << "bounds : " << xmin << " " << xmax << " "
		 << ymin << " " << ymax << std::endl;
	/*_msk->getBounds ( &xminO, &xmaxO, &yminO, &ymaxO );
	std::cerr << "bounds : " << xminO << " " << xmaxO << " "
		 << yminO << " " << ymaxO << std::endl;
	xmin = ( xmin0 <= 0 ) ? 1 : xmin0;
	ymin = ( ymin0 <= 0 ) ? 1 : ymin0;
	xmax = ( xmax0 >= w-1 ) ? w-2 : xmax0;
	ymax = ( ymax0 >= h-1 ) ? h-2 : ymax0;
	*/
	
	// remplacer les valeurs sous le masque
	for ( y = ymin; y <= ymax; ++y )
	{
		//std::cerr << "c : ligne courante " << y << std::endl;
		for ( x = xmin; x <= xmax; ++x )
		{
			//std::cerr << "pos " << x << " " << y << std::endl;
			if ( _msk->LitBool ( x, h - y ) != 0 )
			{
				switch ( _init )
				{
					case 0: // valeurs initiales
						// rien
					break;
					case 1: // un peu de hasard
						if ( rand () < RAND_MAX * 0.8 ) // debug
						{
							px = &(_dest->data[w*y+x]);
							dest2->EcritPixel ( x, y, Pixel (
							std::max(0.0,std::min(255.0,(*px)[0] -127+rand()%254)),
							std::max(0.0,std::min(255.0,(*px)[1] -127+rand()%254)),
							std::max(0.0,std::min(255.0,(*px)[2] -127+rand()%254)) ) );
						}
					break;
					case 2: // beaucoup de hasard
						dest2->EcritPixel ( x, y, Pixel ( 85+rand()%80, 85+rand()%80, 85+rand()%80 ) );
					break;
				}
			}
		}
	}
	
	std::cerr << "b" << std::endl;
	for ( i = 0; i <= _iterations; ++i )
	{
		//std::cout << "#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-" << i << std::endl;
		//std::cout << "iteration " << i << std::endl;
		//dest2->CopierImage ( _dest ); // _dest devient une copie de dest2
		_dest->BlitFrom ( dest2, xmin, xmax, ymin, ymax ); // _dest devient une copie de dest2
		if ( i % 50 == 0 )
		{
			if ( _affichage == 1 )
			{
				dest->toSprite ();
				dest->Afficher ();
				glutSwapBuffers ();
				gettimeofday(&end, NULL);
				seconds  = end.tv_sec  - start.tv_sec;
				useconds = end.tv_usec - start.tv_usec;
				mtime = ((seconds) * 1000 + useconds/1000.0) + 0.5;
				std::cerr << i << " itérations en : " << mtime << " ms" << std::endl;
			}
			std::cerr << i << " itérations" << std::endl;
		}
		bzero ( aGgTTousCanauxOk, (w+tailleVoisinage)*(h+tailleVoisinage) );
		for ( y = ymin; y <= ymax; ++y )
		{
			//std::cerr << "c : ligne courante " << y << std::endl;
			for ( x = xmin; x <= xmax; ++x )
			{
				//std::cerr << "pos " << x << " " << y << std::endl;
				if ( _msk->LitBool ( x, h - y ) != 0 )
				{
					pos = w*y+x;
					/*if ( bStop == true )
					{
						x = xmax+42;
						y = ymax+42;
						continue;
					}*/
					px = &(_dest->data[pos]);
					//std::cerr << "valeur pixel : " << px[0] << " " << px[1] << " " << px[2] << " " << std::endl;
					p[0] = 0.0; p[1] = 0.0; p[2] = 0.0;
					Ggras = calculGgras ( x, y, m22temp, Ggras, _sigma, _tailleVoisinage );
					//std::cerr << "matrice Ggras :" << std::endl;
					//Ggras->afficher ();
					foo = Ggras->calculValEtVecPropres ( lambda1, lambda2, theta1, theta2 );
					if ( foo == -1 ) // un des vecteurs propres est (0,0), on ne peut pas l'utiliser
					{
						//std::cerr << "pos " << x << " " << y << " : vecteur nul, on passe" << std::endl;
						//continue;
					}
					else
					{
						//std::cerr << "lambda1 : " << *lambda1 << std::endl;
						//std::cerr << "lambda2 : " << *lambda2 << std::endl;
						//std::cerr << "theta1 : " << std::endl;
						//theta1->afficher ();
						//std::cerr << "theta2 : " << std::endl;
						//theta2->afficher ();
						
						/*theta1->normaliserVecteur (); // faut-il les normaliser ou pas ? je pense pas, ca coupe les directions, ca fait un flou isotrope
						theta2->normaliserVecteur ();*/
						/*theta1->multiplication ( 2.0, theta1 );
						theta2->multiplication ( 2.0, theta2 );*/
						
						//std::cerr << "theta1 new : " << std::endl;
						//theta1->afficher ();
						//std::cerr << "theta2 new : " << std::endl;
						//theta2->afficher ();
						D = calculD ( *lambda1, *lambda2, theta1, theta2, D );
						//std::cerr << "matrice D :" << std::endl;
						//D->afficher ();
						for ( canal = 0; canal < _canaux; ++canal )
						{
							_dest->computeHessienne ( x, y, canal, H );
							//std::cerr << "matrice H :" << std::endl;
							//H->afficher ();
							//H->multiplication ( 10.0, H );
							D->multiplication ( H, DH );
							//std::cerr << "matrice DH :" << std::endl;
							//DH->afficher ();
							v = DH->trace ();
							p[canal] += v;
						}
						/*if ( x % 10 == 0 && y % 10 == 0 )
								std::cout << "v : " << std::endl
									<< p[0] << std::endl
									<< p[1] << std::endl
									<< p[2] << std::endl;*/
						//pr debug//dest2->EcritPixel ( x, y, Pixel ( 200, 0, 0 ) );
						if ( _canaux == 1 )
						{
							dest2->EcritPixel ( x, y, Pixel ( (*px)[0]+p[0]*1, (*px)[0]+p[0]*1, (*px)[0]+p[0]*1 ) );
						}
						else if ( _canaux == 3 )
						{
							//dest2->EcritPixel ( x, y, Pixel ( (*px)[0]+p[0]*1, (*px)[1]+p[1]*1, (*px)[2]+p[2]*1 ) );
							px2 = &(dest2->data[pos]);
							/*(*px2)[0] = std::max(0.0,std::min(255.0, (*px)[0]+p[0]*1.0 ));
							(*px2)[1] = std::max(0.0,std::min(255.0, (*px)[1]+p[1]*1.0 ));
							(*px2)[2] = std::max(0.0,std::min(255.0, (*px)[2]+p[2]*1.0 ));*/
							(*px2)[0] = (*px)[0]+p[0]*_coeff;
							(*px2)[1] = (*px)[1]+p[1]*_coeff;
							(*px2)[2] = (*px)[2]+p[2]*_coeff;
						}
					}
				}
			}
		}
		//usleep ( 500 );
	}
	dest2->CopierImage ( _dest ); // _dest devient une copie de dest2
	dest->toSprite ();
	dest->Afficher ();
	glutSwapBuffers ();
	
	delete dest2;
	delete Ggras;
	delete lambda1;
	delete lambda2;
	delete theta1;
	delete theta2;
	delete D;
	delete H;
	delete DH;
	delete m22temp;
	
}


void InpaintingThese::stop ( void )
{
	std::cerr << "InpaintingThese::stop ()" << std::endl;
	bStop = true;
}


